Release 10.1A: OpenEdge Development:
Progress 4GL Handbook


Manipulating the browse itself

There are various ways in which you can allow users to change the appearance of the browse at run time. In some cases, you can provide for these changes programmatically so that they are fully under your control. In other cases, you can just let users use the mouse to change the size and shape of the browse to their liking. This section describes some of those capabilities.

Setting the query attribute for the browse

When you define a browse, you must associate it with a previously defined query. This association allows Progress to identify the source for the browse columns and other information. However, at run time you can associate a browse with any query that supplies the columns the browse needs by setting the browse’s QUERY attribute to the handle of the query. The query must supply the same columns as the one you defined the browse with.

When you set the QUERY attribute, Progress immediately refreshes the browse with the results of the new query. If the query is not open then Progress empties the browse.

You can use this capability to define a placeholder query for a browse simply to satisfy the definition, and then to associate the actual query with it at run time. Or you can use this to alternate between two or more queries to display different useful sets of data in the same browse.

To define a different query on the Order table to alternate with the display of Customers of the current Order:

(This alternative shows all the Orders in the database that have the same OrderDate as the currently displayed Order.)

  1. In the Definitions section of h-CustOrderWin5.w, add these lines:
  2. DEFINE BUFFER   bOrder2    FOR Order. 
    DEFINE QUERY    qOrderDate FOR bOrder2 SCROLLING. 
    DEFINE VARIABLE lOrderDate AS LOGICAL    NO-UNDO. 
    

    Buffer bOrder2 is a second buffer for the Order table and query qOrderDate is a query on this buffer. You’ll use this to define a query for other Orders that have the same OrderDate as the currently displayed Order. The lOrderDate flag tells the program whether the user is currently seeing the results of the query on the OrderDate or not.

  3. Add a button to the window named btnOrderDate with the label Show all on this Date.
  4. Define this CHOOSE trigger for the button:
  5. DO: 
        IF NOT lOrderDate THEN 
        DO: 
            /* If the standard query is displayed, open the query for  
               Orderdates and make that the browse's query.  
               Hide the OrderLine browse while we're 
               doing this, and adjust the button label accordingly. */ 
            OPEN QUERY qOrderDate FOR EACH bOrder2 
               WHERE bOrder2.OrderDate = Order.OrderDate. 
            ASSIGN BROWSE OrderBrowse:QUERY = QUERY qOrderDate:HANDLE 
                   /* Signal that we're showing the OrderDate query. */ 
                   lOrderDate = TRUE 
                   hBrowse:HIDDEN = TRUE 
                   SELF:LABEL = "Show Customer's Orders". 
        END. 
        ELSE 
           /* If we're showing the OrderDate query, switch back to the 
                   regular query for the current Customer's Orders. */ 
           ASSIGN BROWSE OrderBrowse:QUERY = QUERY OrderBrowse:HANDLE 
                         lOrderDate = FALSE 
                         hBrowse:HIDDEN = FALSE 
                         SELF:LABEL = "Show all on this Date". 
    END. 
    

    If the lOrderDate flag is not set, then the user sees the default query of Orders for the current Customer. In this case the code:

    1. Opens the other query for all Orders matching the current OrderDate.
    2. Assigns this to be the query for the OrderBrowse.
    3. Reverses the value of the flag variable.
    4. Hides the OrderLine browse because it’s not relevant to this display.
    5. Switches the label of the button to allow the user to change back to the original query.
    6. If the flag is set, then the trigger reverts to the original query and label, resets the flag, and views the OrderLine browse.

      There’s one more step you need to take, so that the procedure always reverts back to the original query if the user clicks one of the First, Next, Last, or Prev buttons.

  6. Create a local copy of the h-ButtonTrig1.i include file, call it h-ButtonTrig2.i, and add these lines to it:
  7. /* h-ButtonTrig2.i -- include file for the First/Next/Prev/Last buttons 
       in h-CustOrderWin5.w. */ 
       GET {1} CustQuery. 
      IF AVAILABLE Customer THEN  
       DISPLAY Customer.CustNum Customer.Name Customer.Address Customer.City  
               Customer.State  
           WITH FRAME CustQuery IN WINDOW CustWin. 
      IF lOrderDate THEN 
          APPLY "CHOOSE" TO btnOrderDate. 
      {&OPEN-BROWSERS-IN-QUERY-CustQuery} 
      APPLY "VALUE-CHANGED" TO OrderBrowse. 
    

    This code runs the trigger block if the flag is true, so that it reverts to the Orders OF Customer query.

  8. Run the procedure now and click the Show all on this Date button. You can see the effect of switching queries for the browse:
Accessing the browse columns

In Chapter 20, "Creating and Using Dynamic Temp-tables and Browses," you will learn how to create a dynamic browse. Even with a static browse it is often useful to get at some of its attributes through its handle. You have already seen how you can use the handle of a browse rather than the BROWSE browse-name static language construct to refer to a browse and access its attributes. The individual columns of the browse also have handles. You can get the handle of any browse column by walking through the list of browse columns from left to right. This section introduces you to this concept, and in later examples you’ll use this technique to act on the columns of a browse. These are the basic attributes you need to identify any column in a browse:

Browse columns have various useful attributes that you can get and, in some cases, set (such as its NAME or LABEL). You can learn about all of these attributes in the online help and in OpenEdge Development: Progress 4GL Reference . Later examples, such as the one in the "Moving columns" section, show how to use a few of these.

Locking columns

You can use the NUM-LOCKED-COLUMNS attribute to prevent one or more browse columns from scrolling out of the browse viewport when the horizontal scrollbar is used. A nonhorizontal-scrolling column is referred to as a locked column.

Locked columns are always the leftmost columns in the browse. In other words, if you set NUM-LOCKED-COLUMNS to 2, the first two columns listed in the DEFINE BROWSE statement are locked. In the next example, the Order Number and Order Date never move out of the browse viewport, no matter which of the remaining fields the user accesses with the horizontal scrollbar.

To experiment with locking columns:

  1. Double-click on the OrderBrowse to go into its property sheet.
  2. Click the Fields button to edit the field list.
  3. Click the Add button and add all the rest of the Order fields to the browse. Don’t add any Customer fields, as they just repeat all the values from the Customer record you’re already displaying above the browse.
  4. Back in the property sheet, set Locked Columns to 2.
  5. The AppBuilder generates a statement from the Locked Columns setting to set the NUM-LOCKED-COLUMNS attribute at run time, because this attribute cannot be set using a keyword in the DEFINE BROWSE statement:

    ASSIGN  
           OrderBrowse:NUM-LOCKED-COLUMNS IN FRAME CustQuery      = 2. 
    

  6. Run the window again. You can scroll through all the rest of the Order columns, but the first two columns are always displayed:
Moving columns

You can use the MOVE-COLUMN( ) method to rearrange the columns within a browse. For example, rather than forcing the users to scroll horizontally to see additional columns, you might allow them to reorder the columns. MOVE-COLUMN takes two arguments:

The following simple example shows how to use the MOVE-COLUMN method along with the START-SEARCH event and the column attributes introduced earlier.

The START-SEARCH event occurs when the user clicks on the column header for a browse with enabled columns. You can use this event to sort by column or for other purposes. In this case, you want to identify which column position was selected and move this column one position to the left.

To rearrange the columns you see first in the viewport without scrolling, define this START-SEARCH trigger block for the OrderBrowse:

DO: 
  DEFINE VARIABLE hColumn AS HANDLE     NO-UNDO. 
  DEFINE VARIABLE iColumn AS INTEGER    NO-UNDO. 
  hColumn = SELF:FIRST-COLUMN. 
  DO iColumn = 1 TO SELF:NUM-COLUMNS: 
     IF hColumn = SELF:CURRENT-COLUMN THEN LEAVE. 
     hColumn = hColumn:NEXT-COLUMN. 
  END. 
  IF iColumn NE 1 THEN  
     SELF:MOVE-COLUMN(iColumn, iColumn - 1). 
END. 

Whenever you enter a trigger block, the SELF keyword evaluates to the handle of the object that initiated the event. In this case, this is the browse itself, not the browse column. The CURRENT-COLUMN attribute returns the handle of the column the user clicked on.

The code initializes the hColumn handle variable to the first column in the browse and then walks through all the columns, looking for the one with the same handle as the browse’s CURRENT-COLUMN. This identifies the sequential position of the one selected. The MOVE-COLUMN method moves this column one position to the left, unless the user selected the first column.

You can also let the user simply drag columns left or right by setting the browse COLUMN-MOVABLE attribute to true or an individual column’s MOVABLE attribute to true, as described in the "Moving the browse" section.

Overlaying objects on browse cells

One useful way to extend the behavior of an updateable browse in a Windows GUI is to overlay an enabled browse cell with another object when the user enters the cell to edit it. For example, on ENTRY of a cell, you could display a combo box or a toggle box. The user selects an entry from the combo box, or checks the toggle box, and you use those values to update the SCREEN-VALUE attribute of the browse cell. The values get committed or undone along with the rest of the row.

There are two problems to overcome:

  1. The first is in calculating the geometry to let you precisely position the overlay widget. You do this by adding the X and Y attributes of the browse cell to the X and Y attributes of the browse and assigning the result to the X and Y attributes of the overlay object.
  2. You might need to move the overlay object if the user accesses the scrollbar or manipulates the browse in some other way. A trigger on the SCROLL-NOTIFY event notifies you when the user scrolls, and you can update the X and Y positioning accordingly.
  3. Note: It is not possible in every instance to capture every manipulation of a browse to reposition an overlayed object, especially if you have made browse columns resizable and so forth. Thus, this technique is not completely foolproof. However, it can be useful in many cases for extending the browse to have visual behavior that users can expect and that is not natively available with the browse.

To overlay the CreditCard field in the Order browse with a combo box:

(Not only is this convenient, but it assures that the user selects only a valid choice for the field.)

  1. Go into the OrderBrowse property sheet, click on the Fields button and move the CreditCard field up toward the head of the list, so that it is visible without horizontal scrolling.
  2. Enable the CreditCard column.
  3. Select the Combo Box icon from the AppBuilder palette and drop it onto any empty spot on the window. It is initially hidden and then moved to the proper location when it’s needed, so it doesn’t matter where it starts out.
  4. In its property sheet, give the new object a name of cCreditCard, check the No-Label toggle box, and provide the three choices shown for the List-Items:
  5. Make the Format X(16) and check the Hidden toggle box from the set of Other Settings at the bottom.
  6. Click OK to accept these settings.
  7. Now you have two objects that will interact: the browse column called CreditCard representing the CreditCard field in the Order table and the combo box called cCreditCard that you will overlay on it.

  8. You might also have to add a line to the procedure’s main block to make sure that the combo box is hidden when the window first comes up:
  9. MAIN-BLOCK: 
    DO ON ERROR   UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK 
      ON END-KEY UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK: 
      RUN enable_UI. 
      cCreditCard:HIDDEN = TRUE. 
      APPLY "VALUE-CHANGED" TO OrderBrowse. 
    END. 
    

Now you need to define a series of trigger blocks to handle the following events on the browse and on the combo box:

To create these trigger blocks:

  1. Define an internal procedure called placeCombo to position and view the combo box on top of the selected browse cell:
  2. PROCEDURE placeCombo: 
    DEFINE VARIABLE hCredit AS HANDLE     NO-UNDO. 
    hCredit = CreditCard:HANDLE IN BROWSE OrderBrowse. 
    IF hCredit:X < 0 OR hCredit:Y < 0 THEN /* Column not on screen */ 
       cCreditCard:VISIBLE IN FRAME CustQuery = FALSE. 
    ELSE DO:  
        ASSIGN cCreditCard:X IN FRAME CustQuery = hCredit:X + OrderBrowse:X 
               cCreditCard:Y IN FRAME CustQuery = hCredit:Y + OrderBrowse:Y 
               cCreditCard:SCREEN-VALUE = CreditCard:SCREEN-VALUE 
               cCreditCard:VISIBLE IN FRAME CustQuery = TRUE. 
               cCreditCard:MOVE-TO-TOP(). 
    END. 
    END PROCEDURE. 
    

    This procedure gets the handle to the CreditCard cell in the browse and checks its X and Y coordinates (which are in pixels). If they are not both greater than zero, then the column is not currently displayed and no action is taken. This might happen in response to a browse event that has scrolled the contents of the browse, for instance. If the column has been scrolled out of the viewport, then the column can’t be updated.

    Otherwise, the code totals the X and Y positions of the browse itself and the CreditCard cell. This is because the X and Y coordinates of the browse are relative to its frame, and the X and Y coordinates of the cell are relative to the browse. Since the combo box coordinates are relative to the frame, you need to add the browse position and the cell position together to get the cell’s position relative to the frame, which is where the combo box will go.

    Next, the code initializes the cCreditCard combo box to the SCREEN-VALUE of the selected cell. Finally, the code makes the combo box visible and uses the MOVE-TO-TOP method to make sure that it’s displayed on top of the browse and not hidden underneath it.

  3. Define an ENTRY trigger for the CreditCard browse column to run the procedure when the user clicks in the column:
  4. DO: 
      RUN placeCombo. 
    END. 
    

  5. Define a LEAVE trigger for the CreditCard column:
  6. DO: 
      cCreditCard:VISIBLE IN FRAME CustQuery = FALSE. 
    END. 
    

    You want this trigger to execute when the user really leaves this column, for example, by selecting another column or another row. The overlay combo has finished its usefulness at that point and you should hide it so that the simple value in the cell itself is shown. However, there is one wrinkle to this.

    As soon as the user clicks in the combo box to change its value, this action leaves the browse cell and then enters the combo box. The LEAVE trigger you just defined therefore hides the combo box, which is not at all what you want! So you have to define an ENTRY trigger for the combo box to make sure it is visible whenever it is being used.

  7. Define an ENTRY trigger for the cCreditCard combo box to make sure it displays whenever it is selected:
  8. DO: 
      SELF:VISIBLE = TRUE. 
    END. 
    

    After the user has selected a value from the combo box, you need to hide the combo box, apply the new value to the browse cell, and move focus back into the browse cell.

  9. To do this, write this VALUE-CHANGED trigger for cCreditCard:
  10. DO: 
      cCreditCard:VISIBLE IN FRAME CustQuery = FALSE. 
      CreditCard:SCREEN-VALUE IN BROWSE OrderBrowse =  
         cCreditCard:SCREEN-VALUE. 
      APPLY "ENTRY" TO CreditCard IN BROWSE OrderBrowse. 
    END. 
    

    Now your changes basically work, but the placement of the combo box is messed up if the user scrolls the browse when the combo box is visible.

  11. Write this SCROLL-NOTIFY trigger for the OrderBrowse to reposition the overlay combo box properly:
  12. DO: 
      RUN placeCombo. 
    END. 
    

    Because you enabled column moving, you need to do the same thing on the START-SEARCH event so that in case the user moves the CreditCard column, the combo box moves with it.

  13. Add this line to the START-SEARCH trigger for the browse:
  14. DO: 
      DEFINE VARIABLE hColumn AS HANDLE     NO-UNDO. 
      DEFINE VARIABLE iColumn AS INTEGER    NO-UNDO. 
      hColumn = SELF:FIRST-COLUMN. 
      DO iColumn = 1 TO SELF:NUM-COLUMNS: 
          IF hColumn = SELF:CURRENT-COLUMN THEN LEAVE. 
          hColumn = hColumn:NEXT-COLUMN. 
      END. 
      IF iColumn NE 1 THEN  
          SELF:MOVE-COLUMN(iColumn, iColumn - 1). 
      RUN placeCombo. 
    END. 
    

To test your changes:

  1. Run the window and select a Credit Card cell. The combo box overlays the cell:
  2. If necessary, edit your code to adjust the width of the Credit Card column and the row height of the browse so that the combo box looks as though it really fits into the cell.
  3. Select the combo box, then select one of the valid choices:
  4. When you leave the field, this becomes the new value for the cell:


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095